﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.ComponentModel;
using Poligono.Core;
using System.Threading;
using System.Data;
using System.Configuration;

namespace Poligono.Data
{
    /// <summary>
    /// Class that will be used to make connections with the database. Will be used to perform SQL commands that require a transactional 
    /// block. There can be only one open class scope, so why be the same singleton.
    /// </summary>
    public class Scope : DisposeControl//, IDisposable
    {

        #region Privates Members


        /// <summary>
        /// Single variable that will be used to implement the Singleton Design Pattern. Attribute ThreadStatic indicates that there will 
        /// only be a single instance variable per thread.
        /// </summary>
        [ThreadStatic]
        private static Scope _Current;
        /// <summary>
        /// Last Scope, when a new scope is opened the current scope will be the Previous
        /// </summary>
        private static Scope _Previous;
        //[ThreadStatic]
        private int _OpenRequests; //When a scope is instantiated, increases. When made a commit, is effected by a decrement.
        private bool _Commit = false; //Variable that is used to determine if it is to perform a commit.
        private bool _Rollback = false; //Variable that is used to determine if it is to perform a commit.
        private bool _IsDone = false;
        private DataAcessProvider _DataAcessProvider;
        //Variable that indicates whether Dispose has been called
        private bool disposed = false;

        #endregion



        #region Class Properties
        /// <summary>
        /// Count how many Scope is open
        /// </summary>
        public int OpenRequests
        {
            get { return _OpenRequests; }
            internal set { _OpenRequests = value; }
        }

        /// <summary>
        /// Get/Set if transaction was completed
        /// </summary>
        public bool IsDone
        {
            get { return _IsDone; }
            set { _IsDone = value; }
        }

        /// <summary>
        /// If there is no instance of the scope, a new one is instantiated. Otherwise, we use to which it is instantiated.
        /// </summary>
        /// <returns>Returns an object of type Scope</returns>
        public static Scope Current
        {
            get
            {

                return Scope._Current;
            }

            //if (scope == null)
            //{
            //    scope = new Scope();
            //}

            //if ((scope._Command == null) || (scope._Connection == null))
            //{
            //    scope.StartConection();
            //    scope.Command.Transaction = scope._Connection.BeginTransaction();
            //}
            //scope.openRequests++;
            //scope.commit = false;
            //scope.rollback = false;
            //return scope;
        }


        /// <summary>
        /// Get Acess Provider. If connection is Close, he will Opened
        /// </summary>
        public DataAcessProvider DataAcessProvider
        {
            get
            {
                using (Current)
                {
                    CheckDispose();
                    if (_DataAcessProvider.Connection.State == System.Data.ConnectionState.Closed)
                        _DataAcessProvider.Connection.Open();
                    return _DataAcessProvider;
                }
            }
        }


        /// <summary>
        /// Connection being used to access the database.
        /// </summary>
        public DbConnection Connection
        {
            get { return DataAcessProvider.Connection; }
        }


        #endregion Class Properties

        /// <summary>
        /// Menssager of erro <see cref="ObjectDisposedException"/>
        /// </summary>
        protected override string MensagemErroObjectDisposedException
        {
            get
            {
                return string.Format("ThreadID: {0} Identity: {0}",
                    Thread.CurrentThread.ManagedThreadId
                    , Thread.CurrentPrincipal.Identity.Name);
            }
        }
        

        #region Class Constructor

        #region Constructor with ConnectionString Enumerable


        /// <summary>
        /// Scope
        /// <remarks>that scope will use a scope alive, or will create a new scope </remarks>
        /// </summary>
        public Scope()
            : this(ScopeChoice.Required)
        {
        }
        /// <summary>
        /// Scope        
        /// </summary>
        public Scope(ScopeChoice scopeChoice)
            : this(scopeChoice, ConnectionStringName.Default)
        {
        }
        /// <summary>
        /// Scope        
        /// </summary>
        public Scope(TransactionType transactionType, ConnectionStringName connectionStringName)
            : this(ScopeChoice.Required, connectionStringName, transactionType)
        {
        }
        /// <summary>
        /// Scope        
        /// </summary>
        public Scope(TransactionType transactionType)
            : this(ScopeChoice.Required, ConnectionStringName.Default, transactionType)
        {
        }
        /// <summary>
        /// Scope        
        /// </summary>
        public Scope(ScopeChoice scopeChoice, ConnectionStringName connectionStringName)
            : this(scopeChoice, connectionStringName, TransactionType.OnlyToKeepScope)
        {
        }
        /// <summary>
        /// Scope        
        /// </summary>
        public Scope(ScopeChoice scopeChoice, ConnectionStringName connectionStringName, TransactionType transactionType)
            : this(scopeChoice, ConfigurationManager.ConnectionStrings[connectionStringName.ToString()], transactionType)
        {
        }
        /// <summary>
        /// Scope
        /// </summary>
        internal Scope(ScopeChoice scopeChoice, ConnectionStringSettings connectionStringSettings, TransactionType transactionType)
            : this(scopeChoice, DbProviderFactories.GetFactory(connectionStringSettings.ProviderName), connectionStringSettings.ConnectionString, transactionType, null)
        {
        }

        #endregion
        /// <summary>
        /// Scope        
        /// </summary>
        public Scope(ScopeChoice scopeChoice, DbProviderFactory providerFactory, string connectionString, TransactionType transactionType, IsolationLevel? isolationLevel)
        {
            commonConstructor(scopeChoice, providerFactory, connectionString, transactionType, isolationLevel);
        }

        private void commonConstructor(ScopeChoice scopeChoice, DbProviderFactory providerFactory, string connectionString, TransactionType transactionType, IsolationLevel? isolationLevel)
        {
            if (NeedChangeScope(scopeChoice, connectionString))
            {
                _DataAcessProvider = new DataAcessProvider(providerFactory, connectionString);
                StackScope();
                ConfigTransaction(transactionType, isolationLevel);
            }
        }

        /*

        /// <summary>
        /// If there is no instance of the Scope for DbCommandUtil, a new one is instantiated. Otherwise, 
        /// we use to which it is instantiated.
        /// </summary>
        /// <returns>Returns an object of type Scope</returns>
        internal Scope GetScopeDbCommandUtil()
        {
            if (_Current == null)
            {
                _Current = new Scope();
            }

            if ((_Current._Command == null) || (_Current.Connection == null))
            {
                _Current.StartConection();
                _Current.Command.Transaction = _Current.Connection.BeginTransaction();
            }
            _Current._Commit = false;
            _Current._Rollback = false;
            return _Current;
        }
         * 
         */
        #endregion Class Constructor



        #region Class Methods


        private void StackScope()
        {
            _Previous = _Current;
            IsDone = false;
            _Current = this;
            _Current.OpenRequests++;
        }

        private void ConfigTransaction(TransactionType transactionType, IsolationLevel? isolationLevel)
        {
            if (Current.DataAcessProvider.Transaction != null)
                throw new InvalidOperationException("try change transaction type is a operation not legal");
            if (transactionType == TransactionType.UseTransaction)
            {
                if (isolationLevel == null)
                    Current.DataAcessProvider.Transaction = Current.Connection.BeginTransaction();
                else
                    Current.DataAcessProvider.Transaction = Current.Connection.BeginTransaction((IsolationLevel)isolationLevel);
            }
        }

        private bool NeedChangeScope(ScopeChoice scopeChoice, string connectionString)
        {
            bool needChange = false;
            switch (scopeChoice)
            {
                case ScopeChoice.Required:
                    needChange = (Current == null);
                    break;
                case ScopeChoice.RequestsNew:
                    needChange = true;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("Choice scope no supported");
            }
            if (Current != null && !needChange)
            {
                if (Current.DataAcessProvider.Connection.ConnectionString != connectionString)
                    throw new InvalidOperationException("when conection changed is necessary new scope");
                Current.OpenRequests++;
            }
            return needChange;

        }



        ///// <summary>
        ///// Method that starts the connection to the database. The data on the database will be achieved by the configuration file: 
        ///// Web.config for Web applications and App.config for Windows applications
        ///// </summary>
        //private void StartConection()
        //{
        //    string provider = System.Configuration.ConfigurationManager.AppSettings.Get("provider");
        //    string connectionString = System.Configuration.ConfigurationManager.AppSettings.Get("connectionString");
        //    _Provider = DbProviderFactories.GetFactory(provider);
        //    //_Connection = _Provider.CreateConnection();
        //    //_Connection.ConnectionString = connectionString;
        //    //_Connection.Open();
        //    _Command = _Provider.CreateCommand();
        //    //_Command.Connection = _Connection;
        //    disposed = false;
        //}

        /// <summary>
        /// If you run this method, will be made to commit the transaction.
        /// </summary>
        public void Terminate()
        {
            _OpenRequests--;
            if (_OpenRequests == 0)
                _Commit = true;
            //if (_Command != null)
            //    _Command.Parameters.Clear();
            _Rollback = false;
        }

        #endregion Class Methods

        #region iDisposable Implementation

        /* DisposeControl solved it

        /// <summary>
        /// Do not make this a virtual method.
        /// </summary>
        public void Dispose()
        {
            //The object will be deallocated by the Dispose method.
            Dispose(true);
            //The SuppressFinalize prevents the GC try to make a Dispose this object if it has already suffered the Dispose.
            GC.SuppressFinalize(this);
        }
        */

        /* Who use it?
        /// <summary>
        /// Do not make this a virtual method. 
        /// </summary>
        internal void DisposeSql(bool rollback)
        {
            this._Rollback = rollback;
            if ((!rollback) && (_OpenRequests == 0))
                this._Commit = true;
            if (_Command != null)
                _Command.Parameters.Clear();
            Dispose();
        }
        */
        /// <summary>
        /// Method that is executed in two different situations. If disposing equals true, the method was called directly or indirectly 
        /// by the User Code. Resources manageable and unmanageable can be deallocated. If disposing equals false, the method was called 
        /// at run time from within a finalizer. Only unmanaged resources can be deallocated.
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing && Current.OpenRequests > 0)
                {
                    if ((_Commit) && (_OpenRequests == 0) && (this.Connection != null))
                    {
                        this.DataAcessProvider.Transaction.Commit();
                        //this.Connection.Close();                        
                        this._Commit = false;
                        this.DataAcessProvider.Dispose();
                        this._DataAcessProvider = null;
                        this.disposed = true;
                    }
                    else
                    {
                        if ((((!_Commit) && (_OpenRequests == 0)) || (_Rollback)) && (this.Connection != null))
                        {
                            _OpenRequests = 0;
                            this.DataAcessProvider.Transaction.Rollback();
                            //this.Connection.Close();
                            this._Commit = false;
                            this.DataAcessProvider.Dispose();
                            this._DataAcessProvider = null;
                            this.disposed = true;
                        }
                    }
                    this._Rollback = true;
                }
            }
        }

        /// <summary>
        /// Finisher: This method is the finisher, which is invoked by the GC to free the resources. All to clearance 
        /// implemented within it will only run when the method is invoked, but we can not know when this will happen and 
        /// important resources may be stuck until this happens.
        /// </summary>
        ~Scope()
        {
            Dispose(false);
        }
        #endregion iDisposable implementation


        #region new Keywords

        /// <summary>
        /// override to operator ==, necessary because we use Dispose
        /// </summary>
        /// <param name="LeftScope">Scope <see cref="Scope"/></param>
        /// <param name="RightScope">Scope <see cref="Scope"/></param>
        /// <returns>return true if is same scope</returns>
        public static bool operator ==(Scope leftScope, Scope rightScope)
        {
            bool leftNull, rightNull;
            leftNull = Object.ReferenceEquals(leftScope, null);
            rightNull = Object.ReferenceEquals(rightScope, null);

            if (leftNull && rightNull)
                return true;
            else if (leftNull || rightNull)
                return false;
            else
                return leftScope.Equals(rightScope);
        }

        /// <summary>
        /// override to operator ==, necessary because we use Dispose
        /// </summary>
        /// <param name="LeftScope">Scope <see cref="Scope"/></param>
        /// <param name="RightScope">Scope <see cref="Scope"/></param>
        /// <returns>return true if isn't same scope</returns>
        public static bool operator !=(Scope LeftScope, Scope RightScope)
        {
            return !(LeftScope == RightScope);
        }

        /// <summary>
        /// HashCode of <see cref="Scope"/> 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        /// <summary>
        /// Equal of <see cref="Scope"/> 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }



        #endregion

    }
}